/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2024-2024.
 * Description: support for RTOS_DEBUG_DUMP_USER_PROCESS
 * Author: douzhaolei
 * Create: 2024-02-26
 */

#include <linux/mm.h>
#include <linux/coredump.h>
#include <linux/ptrace.h>
#include <linux/rtos/coredump_debug.h>
#include <linux/sched/coredump.h>
#include <linux/signal.h>
#ifdef CONFIG_RTOS_KBOX
#include <linux/set_kbox_region.h>
#endif
#include <linux/rtos_dump.h>
#include <linux/rtos_oom_extend.h>
#include <linux/rtos/coredump_debug.h>
#include <linux/sched/task_stack.h>
#include <asm/rtos_dump.h>

#define USER_DUMP_MAX_SIZE 128
#define USER_DUMP_MIN_SIZE (-128)
#define STACK_SIZE 512
#define UINT_BYTE_NUM            (sizeof(unsigned int))
#define LINE_DATA_NUM           8
#define LINE_BYTES              (UINT_BYTE_NUM * LINE_DATA_NUM)
#define BYTE_ALIGN_MASK         (UINT_BYTE_NUM - 1)
#define LINE_ALIGN_MASK         (LINE_BYTES - 1)
#define ADDRESS_ALIGN_MASK      ((-1UL) << BYTE_ALIGN_MASK)
#ifdef CONFIG_RTOS_KBOX
#define MAX_UINT (~0U)
#define MAX_LOG_SIZE    (MAX_UINT / 1024)
#endif

unsigned int dump_user_process_exception_info;
unsigned long stack_addr[STACK_SIZE];
#ifdef CONFIG_RTOS_KBOX
unsigned int __read_mostly dump_user_process_log_size;
int user_process_kbox_region_id;
#endif

#ifdef CONFIG_RTOS_KBOX
int proc_dump_user_process_log_size(struct ctl_table *table, int write,
				void __user *buffer,
				size_t *lenp, loff_t *ppos)
{
	int tmpid, ret;
	unsigned int pre_size, region_size;

	if (!write)
		return proc_dointvec_minmax(table, write, buffer, lenp, ppos);

	pre_size = dump_user_process_log_size;

	ret = proc_dointvec_minmax(table, write, buffer, lenp, ppos);
	if (ret)
		return ret;

	if (dump_user_process_log_size == 0) {
		dump_user_process_log_size = pre_size;
		pr_err("Please do not enter 0, this is not a valid value.\n");
		return -EINVAL;
	}

	if (dump_user_process_log_size > MAX_LOG_SIZE) {
		dump_user_process_log_size = pre_size;
		pr_err("dump_user_process_log_size is too large!\n");
		return -EINVAL;
	}

	region_size = dump_user_process_log_size * 1024;
	tmpid = kern_kbox_register_region("dump_user", region_size, NULL);
	if (tmpid < 0) {
		dump_user_process_log_size = pre_size;
		pr_err("dump_user_process_log_size kbox region failed with %d!\n", tmpid);
		return tmpid;
	}
	user_process_kbox_region_id = tmpid;
	return 0;
}
#endif

static unsigned int get_user_stack_addr(unsigned long address, int bytes)
{
	unsigned long start_addr;
	unsigned long end_addr;
	unsigned long cur_addr;
	unsigned long temp_data;
	unsigned long p;
	unsigned int addr_num = 0;
	int i;

	if (bytes <= 0)
		return -EINVAL;

	start_addr = address & ADDRESS_ALIGN_MASK;
	end_addr = start_addr + DIV_ROUND_UP(bytes, LINE_BYTES) * LINE_BYTES;
	for (cur_addr = start_addr;  cur_addr < end_addr; cur_addr += LINE_BYTES) {
		for (p = cur_addr, i = 0; i < LINE_DATA_NUM; i++, p += UINT_BYTE_NUM) {
			if (!get_user_nofault(temp_data, (const void *)p)) {
				if (temp_data != 0) {
					stack_addr[addr_num] = PAGE_ALIGN(temp_data);
					addr_num++;
				}
			}
		}
	}
	return addr_num;
}

static void show_user_process_vma_total(struct task_struct *p, struct pt_regs *regs)
{
	int is_lib = 0;
	int i;
	unsigned long flags;
	struct mm_struct *mm;
	struct vm_area_struct *vma;
	struct task_struct *t = p;
	unsigned long addr = 0;
	unsigned long addr_num;
	unsigned int rtos_code_seg_flags;

	task_lock(t);
	mm = t->mm;
	if (mm == NULL) {
		task_unlock(t);
		return;
	}

	if (!mmap_read_trylock(mm)) {
		pr_info("%s task[%d] down_read_trylock failed, ignore it.\n", __func__, t->pid);
		task_unlock(t);
		return;
	}
	addr = user_stack_pointer(regs);
	pr_info("Show user process maps:\n");
	addr_num = get_user_stack_addr(addr, STACK_SIZE);
	for (vma = mm->mmap; vma; vma = vma->vm_next) {
		flags = vma->vm_flags;

		/* print code segment only */
		if ((flags & VM_READ) && (flags & VM_EXEC) &&
				!(flags & VM_MAYSHARE) && !(flags & VM_WRITE)) {
		/* match the address in char *stack_addr with the code segment in the maps table */
			rtos_code_seg_flags = 0;
			for (i = 0; i < addr_num; i++) {
				if ((stack_addr[i] >= vma->vm_start) &&
						(stack_addr[i] <= vma->vm_end))
					rtos_code_seg_flags = 1;
			}
			if (rtos_code_seg_flags != 0)
				oom_show_vma_total(vma, t, &is_lib);
		}
	}

	mmap_read_unlock(mm);
	task_unlock(t);
}

static void dump_user_process_debug_info(const kernel_siginfo_t *siginfo, struct task_struct *p)
{
	struct task_struct *task = p;

	pr_info("Show user process debug info:\n");
	pr_info("task: %s pid|tgid: %d|%d Coredump sig:%d\n",
			task->comm, task->pid, task->tgid, siginfo->si_signo);
}

static void show_user_dump_around(struct pt_regs *regs)
{
	pr_info("Show user dump around:\n");
	show_extra_register_data(regs, USER_DUMP_MIN_SIZE, USER_DUMP_MAX_SIZE);
}

static void show_user_dump_stack(struct pt_regs *regs)
{
	unsigned long addr = 0;

	/* Last 128 bytes of the user-state stack is same as sp,skip it */
	addr = user_stack_pointer(regs);
	do_show_data(addr + USER_DUMP_MAX_SIZE, STACK_SIZE - USER_DUMP_MAX_SIZE, "sp more stacks");
}

void show_user_dump_total(struct task_struct *p, const kernel_siginfo_t *siginfo)
{
	struct pt_regs *regs;
#ifdef CONFIG_RTOS_KBOX
	int r;

	r = set_kbox_region(user_process_kbox_region_id);
#endif
	regs = signal_pt_regs();
	dump_user_process_debug_info(siginfo, p);
	dump_userspace_context(p);
	show_user_process_vma_total(p, regs);
	show_user_dump_around(regs);
	show_user_dump_stack(regs);
#ifdef CONFIG_RTOS_KBOX
	restore_kbox_region(r);
#endif
}

void dump_user_exception(struct task_struct *p, const kernel_siginfo_t *siginfo)
{
	if (p == NULL)
		return;
	if (dump_user_process_exception_info != 0)
		show_user_dump_total(p, siginfo);
}
